﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;

[assembly: WebResource("Vincent.AutoAuthCode.err.jpg", "image/jpeg")]
[assembly: WebResource("Vincent.AutoAuthCode.script.js", "text/javascript", PerformSubstitution = true)]
[assembly: WebResource("Vincent.AutoAuthCode.ok.jpg", "image/jpeg")]
[assembly: WebResource("Vincent.AutoAuthCode.loading.gif", "image/gif")]
namespace HAOest.Web.UI.WebControls.AutoAuthCode
{
    /// <summary>
    /// ASP.net验证码
    /// </summary>
    [ToolboxData("<{0}:AuthCode runat=server></{0}:AuthCode>")]
    public class AuthCode : WebControl
    {
        #region 成员
        string textID = "";

        #endregion

        #region 属性
        public string Text
        {
            get
            {
                if (textID == "") GetID();
                string s = "";
                try
                {
                    s = (string)HttpContext.Current.Session[textID];
                }
                catch { }
                return ((string.IsNullOrEmpty(s)) ? "1234" : s);
            }

            set
            {
                if (textID == "") GetID();
                try
                {
                    HttpContext.Current.Session[textID] = value;
                }
                catch { }
            }
        }

        /// <summary>
        /// 指示用户输入的验证码是否正确
        /// </summary>
        [Browsable(false)]
        public bool IsMatch
        {
            get
            {
                if (textID == "") GetID();
                if (textID == "") return false;
                if (HttpContext.Current.Session[textID] == null) return false;
                if (string.IsNullOrEmpty(HttpContext.Current.Session[textID].ToString())) return false;
                if (string.IsNullOrEmpty(HttpContext.Current.Request.Form[textID])) return false;

                if (!this.IsMatchCase)
                    return HttpContext.Current.Request.Form[textID].ToString() == HttpContext.Current.Session[textID].ToString();
                else
                    return HttpContext.Current.Request.Form[textID].ToString().ToLower() == HttpContext.Current.Session[textID].ToString().ToLower();
            }
        }

        /// <summary>
        /// 验证码图像样式
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Category("外观扩展"), Description("验证码图像样式"), TypeConverter(typeof(ImageStyleTypeConver))]
        public ImageStyle ImageStyle
        {
            get
            {
                return imageStyle;
            }
        }
        ImageStyle imageStyle = new ImageStyle();

        /// <summary>
        /// 文本框控件宽度
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("文本框控件宽度")]
        [DefaultValue("50px")]
        [Localizable(false)]
        public Unit TextControlWidth
        {
            get
            {
                return ViewState["TextControlWidth"] == null ? Unit.Pixel(50) : (Unit)(ViewState["TextControlWidth"]);
            }

            set
            {
                ViewState["TextControlWidth"] = value;
            }
        }

        /// <summary>
        /// 提示更换图片信息，该提示信息同时显示于图片上面及图片左边
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("提示更换图片信息，该提示信息同时显示于图片上面及图片左边")]
        [DefaultValue("换一张图片")]
        [Localizable(false)]
        public string NextImgText
        {
            get
            {
                return ViewState["NextImgText"] == null ? "换一张图片" : ViewState["NextImgText"].ToString();
            }

            set
            {
                ViewState["NextImgText"] = value;
            }
        }

        /// <summary>
        /// 是否在图片左边显示提示更换图片信息
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("是否在图片左边显示提示更换图片信息")]
        [DefaultValue(true)]
        [Localizable(false)]
        public bool IsShowNextImgText
        {
            get
            {
                return ViewState["IsShowNextImgText"] == null ? true : (bool)(ViewState["IsShowNextImgText"]);
            }

            set
            {
                ViewState["IsShowNextImgText"] = value;
            }
        }

        /// <summary>
        /// 是否充许换一张图片
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("是否充许换一张图片")]
        [DefaultValue(true)]
        [Localizable(false)]
        public bool EnableNextImgText
        {
            get
            {
                return ViewState["EnableNextImgText"] == null ? true : (bool)(ViewState["EnableNextImgText"]);
            }

            set
            {
                ViewState["EnableNextImgText"] = value;
            }
        }


        /// <summary>
        /// 验证码错误显示方式
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("验证码错误显示方式")]
        [DefaultValue(ErrorShowTypes.ImageOnRight)]
        [Localizable(false)]
        public ErrorShowTypes ErrorShowType
        {
            get
            {
                return ViewState["ErrorShowType"] == null ? ErrorShowTypes.ImageOnRight : (ErrorShowTypes)(ViewState["ErrorShowType"]);
            }

            set
            {
                ViewState["ErrorShowType"] = value;
            }
        }

        /// <summary>
        /// 验证码字符类型,组合枚举值,例如CodeStringTypes.Number|CodeStringTypes.LowerLetter
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("验证码字符类型,组合枚举值")]
        [DefaultValue(CodeStringTypes.Number)]
        [Localizable(false)]
        public CodeStringTypes CodeStringType
        {
            get
            {
                return ViewState["CodeStringType"] == null ? CodeStringTypes.Number : (CodeStringTypes)(ViewState["CodeStringType"]);
            }

            set
            {
                ViewState["CodeStringType"] = value;
            }
        }

        /// <summary>
        /// 验证码字符长度,最少为4
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("验证码字符长度,最少为4")]
        [DefaultValue(4)]
        [Localizable(false)]
        public int CodeStringLength
        {
            get
            {
                int len = ViewState["CodeStringLength"] == null ? 4 : (int)(ViewState["CodeStringLength"]);
                if (len < 4)
                    return 4;
                else
                    return len;
            }

            set
            {
                if (value < 4)
                    ViewState["CodeStringLength"] = 4;
                else
                    ViewState["CodeStringLength"] = value;
            }
        }

        /// <summary>
        /// 验证码图像类型
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("验证码图像类型")]
        [DefaultValue(ImageTypes.SimpleNoisePoint)]
        [Localizable(false)]
        public ImageTypes ImageType
        {
            get
            {
                return ViewState["ImageType"] == null ? ImageTypes.SimpleNoisePoint : (ImageTypes)(ViewState["ImageType"]);
            }

            set
            {
                ViewState["ImageType"] = value;
            }
        }

        /// <summary>
        /// 验证码验证时是否区分大小写
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("验证码验证时是否区分大小写")]
        [DefaultValue(false)]
        [Localizable(false)]
        public bool IsMatchCase
        {
            get
            {
                return ViewState["IsMatchCase"] == null ? false : (bool)(ViewState["IsMatchCase"]);
            }

            set
            {
                ViewState["IsMatchCase"] = value;
            }
        }

        /// <summary>
        /// 控件各部分排列方向,排列的部分包括文本框、图片、"换一张图片"文本
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("控件各部分排列方向,排列的部分包括文本框、图片、\"换一张图片\"文本")]
        [DefaultValue(LayoutDirections.Horizontal)]
        [Localizable(false)]
        public LayoutDirections LayoutDirection
        {
            get
            {
                return ViewState["LayoutDirection"] == null ? LayoutDirections.Horizontal : (LayoutDirections)(ViewState["LayoutDirection"]);
            }

            set
            {
                ViewState["LayoutDirection"] = value;
            }
        }

        /// <summary>
        /// 是否使用客户端脚本验证，验证内容包括是否为空、长度是否正确
        /// </summary>
        [Bindable(false)]
        [Category("外观扩展"), Description("是否使用客户端脚本验证，验证内容包括是否为空、长度是否正确")]
        [DefaultValue(true)]
        [Localizable(false)]
        public bool EnableClientValidate
        {
            get
            {
                return ViewState["EnableClientValidate"] == null ? true : (bool)(ViewState["EnableClientValidate"]);
            }

            set
            {
                ViewState["EnableClientValidate"] = value;
            }
        }

        #endregion

        #region 构造
        /// <summary>
        /// ASP.net验证码
        /// </summary>
        public AuthCode()
        {
        }

        #endregion

        #region 公开函数
        /// <summary>
        /// 清除在验证码中用到的Session
        /// </summary>
        public void ClearSession()
        {
            HttpContext.Current.Session.Remove(textID);
        }
        /// <summary>
        /// 获取验证码图像
        /// </summary>
        /// <param name="codeText">要显示于图像上的文字</param>
        /// <param name="imgType">图像类型</param>
        /// <param name="imgStyle">图像样式</param>
        /// <returns></returns>
        public virtual Bitmap GetBitmap(string codeText, ImageStyle imgStyle)
        {
            Bitmap bmp = BitmapProvide.GetBitmap(ImageType, codeText, imageStyle);
            return bmp;
        }
        #endregion

        #region 重写函数
        #region 输出HTML

        /// <summary>
        /// 输出内容前注册客户端脚本
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            //客户端Ajax请求
            if (!string.IsNullOrEmpty(HttpContext.Current.Request.QueryString["___command"]))
            {
                ParseAjaxCommand(HttpContext.Current.Request.QueryString["___command"], HttpContext.Current.Request.QueryString["___param"]);
                return;
            }

            if (textID == "") GetID();
            this.Text = GetCodeString();

            //输出脚本
            string autoResult = "";
            if (
                this.Page != null &&
                this.Page.IsPostBack &&
                !IsMatch &&
                (ErrorShowType == ErrorShowTypes.ImageAndAlert || ErrorShowType == ErrorShowTypes.AlertMessage)
               )
                autoResult =
               "\n <script>alert(\"验证码错误\");</script>";

            string script = "";
            if (EnableClientValidate) script =
                  "\n <script>" +
                  "\n     function ___authCode_checkCode(){" +
                  "\n         if(document.getElementById('" + textID + "').value==''){alert('请输入验证码!');return false;}" +
                  "\n         else if(document.getElementById('" + textID + "').value.length!=" + this.Text.Length + "){alert('验证码输入长度有误!');return false;}" +
                  "\n         else if(!___IsValidate){alert('验证码输入错误!');return false;}" +
                  "\n         return true;" +
                  "\n     }" +
                  "\n     if (window.navigator.userAgent.indexOf(\"MSIE\")>=1)" +
                  "\n         document.getElementById('" + (this.Page.Form == null ? "form1" : this.Page.Form.ClientID) + "').attachEvent(\"onsubmit\",___authCode_checkCode);" +
                  "\n     else" +
                  "\n         document.forms['" + (this.Page.Form == null ? "form1" : this.Page.Form.ClientID) + "'].onsubmit=___authCode_checkCode;" +
                  "\n </script>\n ";


            //注册脚本
            if (autoResult != "")
                this.Page.ClientScript.RegisterStartupScript(this.Page.GetType(), "___authScript", autoResult);
            if (script != "")
                this.Page.ClientScript.RegisterStartupScript(this.Page.GetType(), "___authScript", script);

            //注册脚本文件
            this.Page.ClientScript.RegisterClientScriptInclude("___ajax", this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "Vincent.AutoAuthCode.script.js"));

            base.OnPreRender(e);
        }

        /// <summary>
        /// 输出控件头部
        /// </summary>
        /// <param name="writer"></param>
        public override void RenderBeginTag(HtmlTextWriter writer)
        {
            if (this.Page == null || this.Page.Form == null)
                return;

            if (this.ID == null)
                this.EnsureID();

            //输出控件头部
            string info =
                "\n <!--####################################################################################-->" +
                "\n <!--#                               Auto Auth-Code 说明                                #-->" +
                "\n <!--#                作者：陈粤雄  联系：Aresh@163.com 时间:2009-05-11                 #-->" +
                "\n <!--#           欢迎使用本控件，你可以免费自由使用本控件，但请保留本说明信息           #-->" +
                "\n <!--####################################################################################-->";

            writer.Write(
                //info +
                "\n <span id=\"" + this.ID + "\">"
                );
        }

        /// <summary>
        /// 输出尾部
        /// </summary>
        /// <param name="writer"></param>
        public override void RenderEndTag(HtmlTextWriter writer)
        {
            if (this.Page == null || this.Page.Form == null)
                return;

            writer.Write("\n </span>\n");
        }

        /// <summary>
        /// 输出中间主体
        /// </summary>
        /// <param name="output"></param>
        protected override void RenderContents(HtmlTextWriter output)
        {
            if (this.Page == null || this.Page.Form == null)
            {
                output.Write("<span>&nbsp;&nbsp;&nbsp;Auto Auth-Code&nbsp;&nbsp;&nbsp;</span>");
                return;
            }

            bool isDrawingBitmap = false;
            string txtValue = "";
            try
            {
                isDrawingBitmap = string.IsNullOrEmpty(HttpContext.Current.Request.QueryString[textID]);
                txtValue = HttpContext.Current.Request.Form[textID];
            }
            catch { }

            if (isDrawingBitmap)
            {
                //换一张图片
                string nextImgStr = "";
                if (EnableNextImgText && IsShowNextImgText) nextImgStr =
                        "\n         <td style=\"padding:0;margin:0;height:25px;line-height:25px;\" valign=\"bottom\">" +
                        "\n             <span style=\"cursor:pointer;color:#ff7755; font-size:12px;text-decoration:underline;\" " +
                        "\n             onclick=\"document.getElementById('" + textID + IdSeparator + "img').src='?" + textID + "=c" + new Random().Next(10000, 55555) + "&___clientRandom='+Math.random()\" >换一张图片</span>" +
                        "\n         </td>";

                //验证码验证信息
                string authStrDisplay = "display:none;";
                if (
                    this.Page != null &&
                    this.Page.IsPostBack &&
                    !IsMatch &&
                    (ErrorShowType == ErrorShowTypes.ImageAndAlert || ErrorShowType == ErrorShowTypes.ImageOnRight)
                   )
                    authStrDisplay = "";

                string authStr =
                    "<td id=\"___authStr\">" +
                    "<img title=\"正在验证...\" style=\"display:none;\" src=\"" + this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "Vincent.AutoAuthCode.loading.gif") + "\" />" +
                    "<img title=\"验证码正确!\" style=\"display:none;\" src=\"" + this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "Vincent.AutoAuthCode.ok.jpg") + "\" />" +
                    "<img title=\"验证码错误!\" style=\"" + authStrDisplay + "\" src=\"" + this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "Vincent.AutoAuthCode.err.jpg") + "\" />" +
                    "</td>";

                //如果为垂直排列则要加</tr><tr>
                string layoutString = "";
                if (this.LayoutDirection == LayoutDirections.Vertical)
                    layoutString = "\n     </tr><tr>";

                //输出HTML
                output.Write(
                    "\n <table border=\"0\" cellpadding=\"0\" cellspacing=\"2\" style=\"height:25px;\" >" +
                    "\n     <tr>" +
                    "\n         <td style=\"padding:0;margin:0;height:25px;\" >" +
                    "\n             <table cellpadding=\"0\" cellspacing=\"0\"><tr><td>" +
                    "\n                 <input type=\"text\" title=\"请输入右边图片显示的字符\" onblur=\"GetIsValidate(this.value);\" onkeyup=\"GetIsValidate(this.value);\" onmouseup=\"GetIsValidate(this.value);\" onchange=\"GetIsValidate(this.value);\" style=\"width:" + TextControlWidth + "\" id=\"" + textID + "\" name=\"" + textID + "\" value=\"" + txtValue + "\" />" +
                    "\n             </td>\n" + authStr +
                    "\n             </tr></table>" +
                    "\n         </td>" +
                    layoutString +
                    "\n         <td style=\"padding:0;margin:0;padding-top:0 !important;padding-top:2px;height:25px;\">" +
                    "\n             <img style=\"cursor:pointer;\" title=\"换一张图片\" id=\"" + textID + IdSeparator + "img\" border=\"0\" alt=\"\" " + (EnableNextImgText ? "onclick=\"this.src='?" + textID + "=c" + new Random().Next(100000, 555555) + "&___clientRandom='+Math.random()\"" : "") + " src=\"?" + textID + "=c" + new Random().Next(555555, 999999) + "\" />" +
                    "\n         </td>" +
                    layoutString +
                    nextImgStr +
                    "\n     </tr>" +
                    "\n </table>\n "
                    );
            }
            else
            {
                WriteImage(Text);
            }
        }

        #endregion

        #endregion

        #region 私有函数

        //获取控件ID
        private void GetID()
        {
            if (this.ID == null) this.EnsureID();
            if (this.ID == null) this.ID = "checkcode_" + this.UniqueID;
            textID = this.ID + this.IdSeparator + "codeText";
        }

        //获取随机验证码字符
        private string GetCodeString()
        {
            //48-57    0-9     
            //65-90    A-Z     
            //97-122   a-z    

            List<int> enableChars = new List<int>();

            //this.CodeStringType的值把合法的字符加入enableChars,然后随机取其中一个
            if ((this.CodeStringType & CodeStringTypes.Number) > 0)
            {
                for (int i = 48; i <= 57; i++)
                    enableChars.Add(i);
            }
            if ((this.CodeStringType & CodeStringTypes.UpperLetter) > 0)
            {
                for (int i = 65; i <= 90; i++)
                    enableChars.Add(i);
            }
            if ((this.CodeStringType & CodeStringTypes.LowerLetter) > 0)
            {
                for (int i = 97; i <= 122; i++)
                    enableChars.Add(i);
            }

            //随机取其中一个
            Random rnd = new Random();
            string result = "";
            for (int i = 0; i < this.CodeStringLength; i++)
            {
                int r = rnd.Next(0, enableChars.Count);
                int c = (int)(enableChars[r]);
                result += ((char)c).ToString();
            }

            return result;
        }

        //写出图像
        private void WriteImage(string codeText)
        {
            if (codeText == null || codeText.Trim() == String.Empty)
                return;

            //获取图像
            Bitmap bmp = GetBitmap(codeText, this.ImageStyle);

            //写出流
            if (bmp != null)
            {
                System.IO.MemoryStream ms = new System.IO.MemoryStream();
                bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);

                try
                {
                    HttpContext.Current.Response.Expires = -1;
                    HttpContext.Current.Response.ExpiresAbsolute = DateTime.Now.AddSeconds(-1);
                    HttpContext.Current.Response.CacheControl = "no-cache";
                    HttpContext.Current.Response.ClearContent();
                    HttpContext.Current.Response.ContentType = "image/Gif";
                    HttpContext.Current.Response.BinaryWrite(ms.ToArray());
                }
                catch { }

                ms.Close();
                ms.Dispose();
                bmp.Dispose();
            }
        }

        //解析客户端ajax命令
        private void ParseAjaxCommand(string command, string param)
        {
            switch (command)
            {
                case "GetIsValidate":
                    HttpContext.Current.Response.Clear();
                    HttpContext.Current.Response.Write(this.IsMatchCase ? Text == param : Text.ToLower() == param.ToLower());
                    HttpContext.Current.Response.End();
                    break;
            }
        }

        #endregion

    }
}
